#include <compilation/task/GraphTask.h>

#include <fstream>

#include <boost/graph/graphviz.hpp>
#include <boost/algorithm/string.hpp>


namespace compilation
{


struct InstructionGraphMark
{
  InstructionGraphMark(void) : agenda(nullptr) {}

  GraphAgenda::Graph::vertex_descriptor vd;
  GraphAgenda* agenda;
};


struct GraphAgendaVertexWriter
{
  GraphAgendaVertexWriter(GraphAgenda::Graph const& gr)
    : graph(gr) {}

  template <InstructionType _Type>
  void print_domination(std::ostream& out, VirtualInstruction* instr, unsigned long from)
  {
    for (VirtualInstruction const* it:
         instruction_cast<_Type>(instr)->getDominationFrontier())
    {
      out << ';' << std::endl;
      out << from << "->"
          << it->getMarkAs<InstructionGraphMark>().vd
          << " [color=red]";
    }
  }

  template <typename _VertexId>
  void operator ()(std::ostream& out, _VertexId const& v)
  {
    GraphAgendaVertex const& vert = graph[v];
    out << "[label=\""
        << vert.instruction_id
        << "\\n"
        << vert.text
        << '"';

    switch (vert.instruction->type())
    {
      case InstructionType::TLabel:
      {
        out << " shape=house]";
        print_domination<TLabel>(out, vert.instruction, v);
        break;
      }
      case InstructionType::TGoto:
      {
        out << " shape=invhouse]";
        print_domination<TGoto>(out, vert.instruction, v);
        break;
      }
      case InstructionType::TIf:
      {
        out << " shape=diamond]";
        break;
      }
      case InstructionType::TExitBlock:
      case InstructionType::TEnterBlock:
      {
        if (vert.instruction->getSPreviousInstruction())
          out << " shape=box]";
        else
          out << " shape=invtriangle]";
        break;
      }
      case InstructionType::TReturn:
      {
        out << " shape=Mcircle]";
        break;
      }
      case InstructionType::TExpression:
      {
        out << ']';
        break;
      }
      case InstructionType::TInstructionUndefined:
      case InstructionType::TInstructionLast:
        assert(false);
    }
  }

  GraphAgenda::Graph const& graph;
};


void GraphAgenda::addInstructionInGraph(
      VirtualInstruction& instruction,
      GraphAgenda& agenda)
{
  if (!instruction.mark)
  {
    GraphAgenda::Vertex v;
    {
      v.instruction    = &instruction;
      v.instruction_id = instruction.getRegistrationIndex();

      std::stringstream ss;
      instruction.print(ss);
      v.text = ss.str();
      boost::replace_all(v.text, "\n", "\\n");
    }
    InstructionGraphMark* gm = new InstructionGraphMark;
    gm->vd = boost::add_vertex(v, agenda.m_graph);
    gm->agenda = &agenda;
    instruction.mark = gm;

    agenda.m_markedInstruction.insert(&instruction);
  }
}


bool GraphAgenda::connectInstructionsInGraph(
    VirtualInstruction& instruction1,
    VirtualInstruction& instruction2)
{
  if (!instruction1.mark && !instruction2.mark) return false;

  if (instruction1.mark && !instruction2.mark)
  {
    InstructionGraphMark* mark =
        reinterpret_cast<InstructionGraphMark*>(instruction1.mark);

    GraphAgenda::addInstructionInGraph(instruction2, *mark->agenda);
  }
  else if (instruction2.mark)
  {
    InstructionGraphMark* mark =
        reinterpret_cast<InstructionGraphMark*>(
          instruction2.mark);

    GraphAgenda::addInstructionInGraph(instruction1, *mark->agenda);
  }

  boost::add_edge(
        reinterpret_cast<InstructionGraphMark*>(instruction1.mark)->vd,
        reinterpret_cast<InstructionGraphMark*>(instruction2.mark)->vd,
        reinterpret_cast<InstructionGraphMark*>(instruction1.mark)
        ->agenda->m_graph);

  return true;
}


GraphTask::GraphTask(VirtualInstruction &instruction)
{ setInstruction(instruction); }


void GraphTask::applyOn(VirtualInstruction &instruction, VirtualAgenda &continuations)
{
  assert(dynamic_cast<const GraphAgenda*>(&continuations));
  GraphAgenda& agenda = (GraphAgenda&) continuations;

  GraphAgenda::addInstructionInGraph(instruction, agenda);

  if (instruction.getSPreviousInstruction())
  {
    boost::add_edge(
          reinterpret_cast<InstructionGraphMark*>(
            instruction.getSPreviousInstruction()->mark)->vd,
          reinterpret_cast<InstructionGraphMark*>(
            getInstruction().mark)->vd,
          agenda.m_graph);
  }
}


VirtualTask *GraphTask::clone(void) const
{ return new GraphTask(*this); }


bool GraphTask::mergeWith(VirtualTask &vtSource)
{
  assert(dynamic_cast<const GraphTask*>(&vtSource));
  GraphTask* source = reinterpret_cast<GraphTask*>(&vtSource);

  return GraphAgenda::connectInstructionsInGraph(
        getInstruction(),
        source->getInstruction());
}


GraphAgenda::~GraphAgenda(void)
{
  std::set<VirtualInstruction*>::iterator iter =
      m_markedInstruction.begin();

  for (; iter != m_markedInstruction.end(); ++iter)
  {
    VirtualInstruction* current = (*iter);
    delete reinterpret_cast<InstructionGraphMark*>(current->mark);
    current->clearMark();
  }

  m_markedInstruction.clear();
}


void GraphAgenda::markInstructionWith(
    VirtualInstruction &instruction,
    VirtualTask &task) {}


bool GraphAgenda::saveAs(const std::string &filename)
{
  std::ofstream output(filename);

  if (!output.is_open())
  {
    std::cerr
        << "Unable to write in "
        << filename
        << " file."
        << std::endl;

    return false;
  }

  GraphAgendaVertexWriter vwr(m_graph);
  boost::default_writer ewr;
  boost::write_graphviz(output, m_graph, vwr, ewr);
  return true;
}


} // compilation
